package net.protocol.diameter.dictionary;

import net.protocol.diameter.Standard;
import net.protocol.diameter.avp.AbstractAVP;
import net.protocol.diameter.avp.DataFormat;

import static net.protocol.diameter.avp.DataFormat.Grouped;
import static net.protocol.diameter.dictionary.RLevel.*;

/**
 * <pre>
 * <p><b>AVP Format:</b>
 *                                         +---------------------+
 *                                         |    AVP Flag rules   |
 *                                         |----+-----+----+-----|----+
 *                     AVP                 |    |     |SHLD| MUST|    |
 * Attribute Name      Code   Data Type    |MUST| MAY | NOT|  NOT|Encr|
 * ----------------------------------------|----+-----+----+-----|----|
 * Proxy-Info          284    Grouped      | M  |     |    | P,V | N  |
 * Proxy-State         33     OctetString  | M  |     |    | P,V | N  |
 * Proxy-Host          280    DiamIdent    | M  |     |    | P,V | N  |
 *
 * &lt;Proxy-Info&gt; ::= &lt; AVP Header: 284 &gt;
 *                  { Proxy-Host }
 *                  { Proxy-State }
 *                * [ AVP ]
 *
 * <p><b>Sample usages:</b>
 *     AVPFormat format = new AVPFormat(284, "Proxy-Info", 0, DataFormat.Grouped,
 *         new AVPFlagRule(RLevel.MUST, RLevel.MUSTNOT, RLevel.MUSTNOT, false),
 *         Standard.Base.name());
 *
 *     format.add(getProxyState()); // add Proxy-State AVPRule
 *     format.add(getProxyHost()); // add Proxy-Host AVPRule
 *
 *     return format;
 *
 *     for (AVPRule e : DiameterFormat) {
 *         ...
 *     }
 *
 *     private AVPRule getProxyState() {
 *         AVPFormat format = new AVPFormat.Builder(33, DataFormat.OctetString)
 *             .name("Proxy-State")
 *             // .vendorId(0)
 *             .rule(new AVPFlagRule(RLevel.MUST, RLevel.MUSTNOT, RLevel.MUSTNOT, false))
 *             // .standard(Standard.Base.name())
 *             // .description(...)
 *             .build();
 *             
 *         return new AVPRule(format, AVPRule.Level.Required, 1, 1);
 *     }
 * </pre>
 *
 * @author jinhongw@gmail.com
 */
public class AVPFormat extends DiameterFormat {
    private final int vendorId;
    private final DataFormat dataFormat;
    private final AVPFlagRule flagRule;
    private final String standard;
    
    private final String description;

	public AVPFormat(int code, String name, int vendorId,
			DataFormat dataFormat, AVPFlagRule flagRule, String standard) {
		this(code, name, vendorId, dataFormat, flagRule, standard, null);
	}
	
    /**
	 * 
	 * @param code
	 *            AVP Code
	 * @param name
	 *            AVP Attribute Name
	 * @param vendorId
	 *            Vendor-ID
	 * @param dataFormat
	 *            AVP Data Type
	 * @param flagRule
	 *            AVP Flag Rule
	 * @param standard
	 *            Diameter standard(Sh,Gx,Gy)
	 * @param description
	 *            AVP description
	 * 
	 * @throws IllegalArgumentException
	 *             if code <= 0 or vendorId < 0 or dataFormat is null or
	 *             flagRule is null or standard is null or must has vendor but
	 *             vendorId <= 0 or must not has vendor but vendorId != 0
	 */
	public AVPFormat(int code, String name, int vendorId,
			DataFormat dataFormat, AVPFlagRule flagRule, String standard,
			String description) {
        super(code, name);

        if (code <= 0)
            throw new IllegalArgumentException("Invalid AVP-Code: " + code);
        if (vendorId < 0)
            throw new IllegalArgumentException(
                    "Invalid Vendor-Id: " + vendorId + "(>=0)");

        if (dataFormat == null)
            throw new IllegalArgumentException("DataFormat is null");
        if (flagRule == null)
            throw new IllegalArgumentException("AVPFlagRule is null");
        if (standard == null)
            throw new IllegalArgumentException("Standard is null");

        if ((flagRule.getVendor() == MUSTNOT) && vendorId != 0)
            throw new IllegalArgumentException(
                    "Invalid Vendor-Id: " + vendorId + "(0)");
        if ((flagRule.getVendor() == MUST) && vendorId <= 0)
            throw new IllegalArgumentException(
                    "Invalid Vendor-Id: " + vendorId + "(> 0)");

        this.vendorId = vendorId;
        this.dataFormat = dataFormat;
        this.flagRule = flagRule;
        this.standard = standard;
        
        this.description = description;
    }

    /**
     * @return AVP Data Type
     */
    public DataFormat getDataFormat() {
        return dataFormat;
    }

    /**
     * Returns this AVPFormat's Vendor-ID
     *
     * @return Vendor-ID
     */
    public int getVendorId() {
        return vendorId;
    }

    public AVPFlagRule getFlagRule() {
        return flagRule;
    }

    public String getStandard() {
        return standard;
    }

    public String getDescription() {
		return description;
	}

	/**
     * Returns <tt>false</tt> indicating this <code>DiameterFormat</code> is
     * <code>AVPFormat</code> dataFormat.
     *
     * @return false if this <code>DiameterFormat</code> is
     *         <code>AVPFormat</code>
     */
    @Override
    public final boolean isMessage() {
        return false;
    }

    @Override
    public boolean isGroup() {
        return (dataFormat == Grouped) ? true : false;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result
                + ((dataFormat == null) ? 0 : dataFormat.hashCode());
        result = prime * result
                + ((flagRule == null) ? 0 : flagRule.hashCode());
        result = prime * result
                + ((standard == null) ? 0 : standard.hashCode());
        result = prime * result + vendorId;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        AVPFormat other = (AVPFormat) obj;
        if (dataFormat != other.dataFormat)
            return false;
        if (flagRule == null) {
            if (other.flagRule != null)
                return false;
        } else if (!flagRule.equals(other.flagRule))
            return false;
        if (standard == null) {
            if (other.standard != null)
                return false;
        } else if (!standard.equals(other.standard))
            return false;
        if (vendorId != other.vendorId)
            return false;
        return true;
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(getClass().getSimpleName()).append(" [");
        buf.append("code=").append(getCode()).append(", ");
        buf.append("name=").append(getName()).append(", ");
        buf.append("dataFormat=").append(dataFormat).append(", ");
        buf.append("vendorId=").append(vendorId).append(", ");
        buf.append("standard=").append(standard).append(", ");
        buf.append(flagRule);
        if (isMessage() || isGroup()) buf.append(", rules=").append(rules);
        buf.append("]");
        return buf.toString();
    }

    public static class Builder {
        // Required parameters
        private final int code;
        private final DataFormat dataFormat;

        // Optional parameters - initialized to default values
        private String name;
        private int vendorId = AbstractAVP.DEFAULT_VENDOR_ID;
        private AVPFlagRule flagRule = new AVPFlagRule();
        private String standard = Standard.Base.name();
        private String description;

        /**
         * @param code AVP Code(264-Origin-Host)
         * @param dataFormat AVP Data Formats(Basic and Derived)
         */
        public Builder(int code, DataFormat dataFormat) {
            this.code = code;
            this.dataFormat = dataFormat;
        }

        /**
         * @param name AVP Attribute Name(Origin-Host)
         * @return This Builder
         */
        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder vendorId(int vendorId) {
            this.vendorId = vendorId;
            return this;
        }

        public Builder rule(AVPFlagRule flagRule) {
            this.flagRule = flagRule;
            return this;
        }

        public Builder standard(String standard) {
            this.standard = standard;
            return this;
        }
        
        public Builder description(String description) {
            this.description = description;
            return this;
        }

        public AVPFormat build() {
            return new AVPFormat(code, name, vendorId, dataFormat, flagRule, standard, description);
        }
    }
}